package utils

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"go.uber.org/zap"
	"io/ioutil"
	"net"
	"net/http"
	"sync"
	"time"

	"github.com/flipped-aurora/gin-vue-admin/server/global"
)

// ============ 1. loginCode 换 openid ============
type Code2SessionResp struct {
	OpenID     string `json:"openid"`
	SessionKey string `json:"session_key"`
	UnionID    string `json:"unionid,omitempty"`
	ErrCode    int    `json:"errcode"`
	ErrMsg     string `json:"errmsg"`
}

func Code2Session(loginCode string) (*Code2SessionResp, error) {
	if loginCode == "" {
		return nil, errors.New("loginCode不能为空")
	}

	client := &http.Client{Timeout: 5 * time.Second}
	url := fmt.Sprintf(
		"https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
		global.GVA_CONFIG.Wx.Miniapp.Appid,
		global.GVA_CONFIG.Wx.Miniapp.Secret,
		loginCode,
	)

	resp, err := client.Get(url)
	if err != nil {
		global.GVA_LOG.Error("微信jscode2session接口请求失败", zap.Error(err), zap.String("url", url))
		if netErr, ok := err.(net.Error); ok {
			if netErr.Timeout() {
				return nil, errors.New("请求微信接口超时")
			}
			return nil, errors.New("网络错误: " + netErr.Error())
		}
		return nil, errors.New("请求微信接口失败: " + err.Error())
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := ioutil.ReadAll(resp.Body)
		global.GVA_LOG.Error("微信jscode2session接口返回非200状态码",
			zap.Int("statusCode", resp.StatusCode),
			zap.String("responseBody", string(body)))
		return nil, fmt.Errorf("微信接口返回错误状态码: %d", resp.StatusCode)
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		global.GVA_LOG.Error("读取微信jscode2session接口响应失败", zap.Error(err))
		return nil, errors.New("读取微信响应失败: " + err.Error())
	}

	var data Code2SessionResp
	if err := json.Unmarshal(body, &data); err != nil {
		global.GVA_LOG.Error("微信jscode2session返回数据解析失败",
			zap.Error(err),
			zap.String("responseBody", string(body)))
		return nil, errors.New("解析微信响应失败: " + err.Error())
	}

	if data.ErrCode != 0 {
		global.GVA_LOG.Error("微信jscode2session接口返回错误码",
			zap.Int("errcode", data.ErrCode),
			zap.String("errmsg", data.ErrMsg))
		return nil, fmt.Errorf("微信接口错误: %d, %s", data.ErrCode, data.ErrMsg)
	}

	return &data, nil
}

// ============ 2. phoneCode 换手机号 ============
type PhoneInfo struct {
	PhoneNumber     string `json:"phoneNumber"`
	PurePhoneNumber string `json:"purePhoneNumber"`
	CountryCode     string `json:"countryCode"`
}

type GetPhoneResp struct {
	ErrCode int       `json:"errcode"`
	ErrMsg  string    `json:"errmsg"`
	Info    PhoneInfo `json:"phone_info"`
}

func GetPhoneNumber(phoneCode string) (*PhoneInfo, error) {
	if phoneCode == "" {
		return nil, errors.New("phoneCode不能为空")
	}

	// 1. 取 access_token
	token, err := getAccessToken()
	if err != nil {
		global.GVA_LOG.Error("获取access_token失败", zap.Error(err))
		return nil, fmt.Errorf("获取微信授权失败: %w", err)
	}

	// 2. 调微信接口
	reqBody := map[string]string{"code": phoneCode}
	bodyJson, err := json.Marshal(reqBody)
	if err != nil {
		global.GVA_LOG.Error("序列化请求体失败", zap.Error(err))
		return nil, errors.New("请求参数序列化失败")
	}

	url := fmt.Sprintf(
		"https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=%s",
		token,
	)

	client := &http.Client{Timeout: 5 * time.Second}
	resp, err := client.Post(url, "application/json", bytes.NewReader(bodyJson))
	if err != nil {
		global.GVA_LOG.Error("微信获取手机号接口请求失败", zap.Error(err), zap.String("url", url))
		if netErr, ok := err.(net.Error); ok {
			if netErr.Timeout() {
				return nil, errors.New("请求微信接口超时")
			}
			return nil, errors.New("网络错误: " + netErr.Error())
		}
		return nil, errors.New("请求微信接口失败: " + err.Error())
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := ioutil.ReadAll(resp.Body)
		global.GVA_LOG.Error("微信获取手机号接口返回非200状态码",
			zap.Int("statusCode", resp.StatusCode),
			zap.String("responseBody", string(body)))
		return nil, fmt.Errorf("微信接口返回错误状态码: %d", resp.StatusCode)
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		global.GVA_LOG.Error("读取微信获取手机号接口响应失败", zap.Error(err))
		return nil, errors.New("读取微信响应失败: " + err.Error())
	}

	var data GetPhoneResp
	if err := json.Unmarshal(body, &data); err != nil {
		global.GVA_LOG.Error("微信获取手机号返回数据解析失败",
			zap.Error(err),
			zap.String("responseBody", string(body)))
		return nil, errors.New("解析微信响应失败: " + err.Error())
	}

	if data.ErrCode != 0 {
		global.GVA_LOG.Error("微信获取手机号接口返回错误码",
			zap.Int("errcode", data.ErrCode),
			zap.String("errmsg", data.ErrMsg))
		return nil, fmt.Errorf("微信接口错误: %d, %s", data.ErrCode, data.ErrMsg)
	}

	return &data.Info, nil
}

/* -------------- access_token 缓存 -------------- */
type accessToken struct {
	Token  string
	Expire int64
}

var (
	atCache accessToken
	atMutex sync.Mutex
)

func getAccessToken() (string, error) {
	atMutex.Lock()
	defer atMutex.Unlock()
	now := time.Now().Unix()
	if atCache.Token != "" && now < atCache.Expire-60 {
		return atCache.Token, nil
	}

	// 检查配置是否存在
	appID := global.GVA_CONFIG.Wx.Miniapp.Appid
	appSecret := global.GVA_CONFIG.Wx.Miniapp.Secret
	if appID == "" || appSecret == "" {
		return "", errors.New("微信小程序appid或secret未配置")
	}

	url := fmt.Sprintf(
		"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s",
		appID,
		appSecret,
	)

	client := &http.Client{Timeout: 5 * time.Second}
	resp, err := client.Get(url)
	if err != nil {
		global.GVA_LOG.Error("微信获取access_token接口请求失败", zap.Error(err), zap.String("url", url))
		if netErr, ok := err.(net.Error); ok {
			if netErr.Timeout() {
				return "", errors.New("请求微信接口超时")
			}
			return "", errors.New("网络错误: " + netErr.Error())
		}
		return "", errors.New("请求微信接口失败: " + err.Error())
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := ioutil.ReadAll(resp.Body)
		global.GVA_LOG.Error("微信获取access_token接口返回非200状态码",
			zap.Int("statusCode", resp.StatusCode),
			zap.String("responseBody", string(body)))
		return "", fmt.Errorf("微信接口返回错误状态码: %d", resp.StatusCode)
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		global.GVA_LOG.Error("读取微信获取access_token接口响应失败", zap.Error(err))
		return "", errors.New("读取微信响应失败: " + err.Error())
	}

	var res struct {
		AccessToken string `json:"access_token"`
		ExpiresIn   int64  `json:"expires_in"`
		ErrCode     int    `json:"errcode"`
		ErrMsg      string `json:"errmsg"`
	}
	if err := json.Unmarshal(body, &res); err != nil {
		global.GVA_LOG.Error("微信获取access_token返回数据解析失败",
			zap.Error(err),
			zap.String("responseBody", string(body)))
		return "", errors.New("解析微信响应失败: " + err.Error())
	}

	if res.ErrCode != 0 {
		global.GVA_LOG.Error("微信获取access_token接口返回错误码",
			zap.Int("errcode", res.ErrCode),
			zap.String("errmsg", res.ErrMsg))
		return "", fmt.Errorf("微信接口错误: %d, %s", res.ErrCode, res.ErrMsg)
	}

	atCache = accessToken{
		Token:  res.AccessToken,
		Expire: now + res.ExpiresIn,
	}
	return atCache.Token, nil
}
